/**
 * Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved.
 *
 * BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @file    bme68xLibrary.cpp
 * @date    8 Feb 2022
 * @version 1.1.40407
 *
 */

#include <string.h>

#include "bme68xLibrary.h"
#include "mw_log_conf.h"

extern I2C_HandleTypeDef hi2c1;
extern uint8_t BME_address;
/* Maximum transaction size. Field size 17 x 3 */
#define BME68X_MAX_READ_LENGTH 51

#ifdef ARDUINO_ARCH_ESP32
#define BME68X_I2C_BUFFER_SIZE I2C_BUFFER_LENGTH

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_MBED
/* Assuming all MBED implementations of Wire have 256 byte sized buffers */
#define BME68X_I2C_BUFFER_SIZE 256

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_ESP8266
#define BME68X_I2C_BUFFER_SIZE BUFFER_LENGTH

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_AVR
#define BME68X_I2C_BUFFER_SIZE BUFFER_LENGTH

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_NRF52
#define BME68X_I2C_BUFFER_SIZE SERIAL_BUFFER_SIZE

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_SAMD
/* Assuming all Arduino's and Adafruit's SAMD
 * implementations of Wire have 256 byte sized buffers */
#define BME68X_I2C_BUFFER_SIZE 256

#define BME68X_BURST_SPI_TRANSFER
#endif

#ifdef ARDUINO_ARCH_SAM
#define BME68X_I2C_BUFFER_SIZE BUFFER_LENGTH

#define BME68X_BURST_SPI_TRANSFER
#endif

/* Optimistically assume support for at least 64 byte reads */
#ifndef BME68X_I2C_BUFFER_SIZE
#define BME68X_I2C_BUFFER_SIZE 64
#endif

#if BME68X_MAX_READ_LENGTH > BME68X_I2C_BUFFER_SIZE
#warning "Wire read requires a larger buffer size. Use SPI"
#endif

int8_t status01;

bme68xScommT comm;
bme68xDev bme6;
bme68xConf conf;
bme68xHeatrConf heatrConf;
bme68xData sensorData[3];
uint8_t nFields, iFields;
uint8_t lastOpMode;
uint32_t _meas_start = 0;
uint16_t _meas_period = 0;
float temperature;
uint32_t pressure;
float humidity;
uint32_t gas_resistance;
float gasReference = 250000;

void Bme68x(void)
{
	comm.i2c.wireobj = NULL;
	comm.i2c.i2cAddr = 0;
	comm.spi.cs = 0;
	status01 = BME68X_OK;
	memset(&bme6, 0, sizeof(bme6));
	memset(&conf, 0, sizeof(conf));
	memset(&heatrConf, 0, sizeof(heatrConf));
	memset(sensorData, 0, sizeof(sensorData));
	bme6.amb_temp = 25; /* Typical room temperature in Celsius */
	nFields = 0;
	iFields = 0;
	lastOpMode = BME68X_SLEEP_MODE;
}

/**
 * @brief Function to initialize the sensor based on the Wire library
 */
void begin01(uint8_t i2cAddr, I2C_HandleTypeDef *i2c, bme68x_delay_us_fptr_t idleTask)
{
	comm.i2c.i2cAddr = i2cAddr;
	comm.i2c.wireobj = &hi2c1;
	bme6.intf = BME68X_I2C_INTF;
	bme6.read = bme68xI2cRead;
	bme6.write = bme68xI2cWrite;
	bme6.delay_us = bme68xDelayUs;
	bme6.intf_ptr = &comm;
	bme6.amb_temp = 25;
	/*
	if ((bme6 == NULL) || (bme6->read == NULL) ||
		(bme6->write == NULL) || (bme6->delay_us == NULL))
    {
        rslt = BME68X_E_NULL_PTR;
    }
	*/
	status01 = bme68x_init(&bme6);
}

/**
 * @brief Function to read a register
 */
uint8_t readReg01(uint8_t regAddr)
{
	uint8_t regData;
	readReg02(regAddr, &regData, 1);
	return regData;
}

/**
 * @brief Function to read multiple registers
 */
void readReg02(uint8_t regAddr, uint8_t *regData, uint32_t length)
{
	status01 = bme68x_get_regs(regAddr, regData, length, &bme6);
}

/**
 * @brief Function to write data to a register
 */
void writeReg01(uint8_t regAddr, uint8_t regData)
{
	status01 = bme68x_set_regs(&regAddr, &regData, 1, &bme6);
}

/**
 * @brief Function to write multiple registers
 */
void writeReg02(uint8_t *regAddr, const uint8_t *regData, uint32_t length)
{
	status01 = bme68x_set_regs(regAddr, regData, length, &bme6);
}

/**
 * @brief Function to trigger a soft reset
 */
void softReset(void)
{
	status01 = bme68x_soft_reset(&bme6);
}

/**
 * @brief Function to set the ambient temperature for better configuration
 */ 
void setAmbientTemp(int8_t temp)
{
	bme6.amb_temp = temp;
}

/**
 * @brief Function to get the measurement duration in microseconds
 */
uint32_t getMeasDur(uint8_t opMode)
{
	if (opMode == BME68X_SLEEP_MODE)
		opMode = lastOpMode;

	return bme68x_get_meas_dur(opMode, &conf, &bme6);
}

/**
 * @brief Function to set the operation mode
 */
void setOpMode(uint8_t opMode)
{
	status01 = bme68x_set_op_mode(opMode, &bme6);
	if ((status01 == BME68X_OK) && (opMode != BME68X_SLEEP_MODE))
		lastOpMode = opMode;
}

/**
 * @brief Function to get the operation mode
 */
uint8_t getOpMode(void)
{
	uint8_t opMode;
	status01 = bme68x_get_op_mode(&opMode, &bme6);
	return opMode;
}

/**
 * @brief Function to get the Temperature, Pressure and Humidity over-sampling
 */
/*
void getTPH(uint8_t &osHum, uint8_t &osTemp, uint8_t &osPres)
{
	status = bme68x_get_conf(&conf, &bme6);

	if (status == BME68X_OK)
	{
		osHum = conf.os_hum;
		osTemp = conf.os_temp;
		osPres = conf.os_pres;
	}
}
*/
/**
 * @brief Function to set the Temperature, Pressure and Humidity over-sampling
 */
void setTPH(uint8_t osTemp, uint8_t osPres, uint8_t osHum)
{
	status01 = bme68x_get_conf(&conf, &bme6);

	if (status01 == BME68X_OK)
	{
		conf.os_hum = osHum;
		conf.os_temp = osTemp;
		conf.os_pres = osPres;

		status01 = bme68x_set_conf(&conf, &bme6);
	}
}

/**
 * @brief Function to get the filter configuration
 */
uint8_t getFilter(void)
{
	status01 = bme68x_get_conf(&conf, &bme6);

	return conf.filter;
}

/**
 * @brief Function to set the filter configuration
 */
void setFilter(uint8_t filter)
{
	status01 = bme68x_get_conf(&conf, &bme6);

	if (status01 == BME68X_OK)
	{
		conf.filter = filter;

		status01 = bme68x_set_conf(&conf, &bme6);
	}
}

/**
 * @brief Function to get the sleep duration during Sequential mode
 */
uint8_t getSeqSleep(void)
{
	status01 = bme68x_get_conf(&conf, &bme6);

	return conf.odr;
}

/**
 * @brief Function to set the sleep duration during Sequential mode
 */
void setSeqSleep(uint8_t odr)
{
	status01 = bme68x_get_conf(&conf, &bme6);

	if (status01 == BME68X_OK)
	{
		conf.odr = odr;

		status01 = bme68x_set_conf(&conf, &bme6);
	}
}

/**
 * @brief Function to set the heater profile for Forced mode
 */
void setHeaterProf01(uint16_t temp, uint16_t dur)
{
	heatrConf.enable = BME68X_ENABLE;
	heatrConf.heatr_temp = temp;
	heatrConf.heatr_dur = dur;

	status01 = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatrConf, &bme6);
}

/**
 * @brief Function to set the heater profile for Sequential mode
 */
void setHeaterProf02(uint16_t *temp, uint16_t *dur, uint8_t profileLen)
{
	heatrConf.enable = BME68X_ENABLE;
	heatrConf.heatr_temp_prof = temp;
	heatrConf.heatr_dur_prof = dur;
	heatrConf.profile_len = profileLen;

	status01= bme68x_set_heatr_conf(BME68X_SEQUENTIAL_MODE, &heatrConf, &bme6);

}

/**
 * @brief Function to set the heater profile for Parallel mode
 */
void setHeaterProf03(uint16_t *temp, uint16_t *mul, uint16_t sharedHeatrDur, uint8_t profileLen)
{
	heatrConf.enable = BME68X_ENABLE;
	heatrConf.heatr_temp_prof = temp;
	heatrConf.heatr_dur_prof = mul;
	heatrConf.shared_heatr_dur = sharedHeatrDur;
	heatrConf.profile_len = profileLen;

	status01 = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &heatrConf, &bme6);
}

/**
 * @brief Function to fetch data from the sensor into the local buffer
 */
uint8_t fetchData(void)
{
	nFields = 0;
	status01 = bme68x_get_data(lastOpMode, sensorData, &nFields, &bme6);
	iFields = 0;
	return nFields;
}

/**
 * @brief Function to get whole sensor data
 */
bme68xData* getAllData(void)
{
	return sensorData;
}

/**
 * @brief Function to get the BME68x heater configuration
 */
const bme68xHeatrConf getHeaterConfiguration(void)
{
	return heatrConf;
}

/**
 * @brief Function to retrieve the sensor's unique ID
 */
uint32_t getUniqueId(void)
{
    uint8_t id_regs[4];
    uint32_t uid;
    readReg02(BME68X_REG_UNIQUE_ID, id_regs, 4);

    uint32_t id1 = ((uint32_t) id_regs[3] + ((uint32_t) id_regs[2] << 8)) & 0x7fff;
    uid = (id1 << 16) + (((uint32_t) id_regs[1]) << 8) + (uint32_t) id_regs[0];

    return uid;
}

/**
 * @brief Function to get the error code of the interface functions
 */
BME68X_INTF_RET_TYPE intfError(void)
{
	return bme6.intf_rslt;
}

/**
 * @brief Function to check if an error / warning has occurred
 */
int8_t checkStatus(void)
{
	if (status01 < BME68X_OK)
	{
		return BME68X_ERROR;
	}
	else if(status01 > BME68X_OK)
	{
		return BME68X_WARNING;
	}
	else
	{
		return BME68X_OK;
	}
}

/**
 * @brief Function to get a brief text description of the error
 */
String statusString(void)
{
	String ret = "";
	switch (status01)
	{
	case BME68X_OK:
		/* Don't return a text for OK */
		break;
	case BME68X_E_NULL_PTR:
		ret = "Null pointer";
		break;
	case BME68X_E_COM_FAIL:
		ret = "Communication failure";
		break;
	case BME68X_E_DEV_NOT_FOUND:
		ret = "Sensor not found";
		break;
	case BME68X_E_INVALID_LENGTH:
		ret = "Invalid length";
		break;
	case BME68X_W_DEFINE_OP_MODE:
		ret = "Set the operation mode";
		break;
	case BME68X_W_NO_NEW_DATA:
		ret = "No new data";
		break;
	case BME68X_W_DEFINE_SHD_HEATR_DUR:
		ret = "Set the shared heater duration";
		break;
	default:
		ret = "Undefined error code";
	}

	return ret;
}

/**
 * @brief Function that implements the default microsecond delay callback
 */
void bme68xDelayUs(uint32_t periodUs, void *intfPtr) {
    (void) intfPtr;
    HAL_Delay(periodUs / 1000);
}

/**
 * @brief Function that implements the default I2C write transaction
 */
int8_t bme68xI2cWrite(uint8_t regAddr, const uint8_t *regData,
        uint32_t length, void *intfPtr) {
    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */
	HAL_StatusTypeDef error = 0;

	error = HAL_I2C_Mem_Write(&hi2c1, (BME_address << 1), regAddr, 1,
		(uint8_t *)regData, length, 10);
	
	if (error == HAL_OK) {
		rslt = 0;
	} else{
		MW_LOG( TS_OFF, VLEVEL_H,  "HAL_I2C_Mem_Write Error %d\r\n", error);
		rslt = -1;
	}
	/*
	 * The parameter dev_id can be used as a variable to store the I2C address of the device
	 */

	/*
	 * Data on the bus should be like
	 * |------------+---------------------|
	 * | I2C action | Data                |
	 * |------------+---------------------|
	 * | Start      | -                   |
	 * | Write      | (reg_addr)          |
	 * | Write      | (reg_data[0])       |
	 * | Write      | (....)              |
	 * | Write      | (reg_data[len - 1]) |
	 * | Stop       | -                   |
	 * |------------+---------------------|
	 */
	return rslt;
}

/**
 * @brief Function that implements the default I2C read transaction
 */ 
int8_t bme68xI2cRead(uint8_t regAddr, uint8_t *regData, uint32_t length,
        void *intfPtr) {
    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */
	HAL_StatusTypeDef error = 0;
	error = HAL_I2C_Mem_Read(&hi2c1, (BME_address << 1), regAddr,
		I2C_MEMADD_SIZE_8BIT, regData, length, 10);
	
	if (error == HAL_OK) {
		rslt = 0;
	}
	else{
		MW_LOG( TS_ON, VLEVEL_H,  "HAL_I2C_Mem_READ ERROR %d\r\n", error);
		rslt = -1;
	}
	/*
	 * The parameter dev_id can be used as a variable to store the I2C address of the device
	 */

	/*
	 * Data on the bus should be like
	 * |------------+---------------------|
	 * | I2C action | Data                |
	 * |------------+---------------------|
	 * | Start      | -                   |
	 * | Write      | (reg_addr)          |
	 * | Stop       | -                   |
	 * | Start      | -                   |
	 * | Read       | (reg_data[0])       |
	 * | Read       | (....)              |
	 * | Read       | (reg_data[len - 1]) |
	 * | Stop       | -                   |
	 * |------------+---------------------|
	 */
	//MW_LOG( TS_ON, VLEVEL_H,  "user_i2c_read rslt: %d\r\n", rslt);

	return rslt;
}

uint8_t getData01(bme68xData *data) {
	if (lastOpMode == BME68X_FORCED_MODE)
	{
		*data = sensorData[0];
	} else
	{
		if (nFields)
		{
			/* iFields spans from 0-2 while nFields spans from
			 * 0-3, where 0 means that there is no new data
			 */
			*data = sensorData[iFields];
			iFields++;

			/* Limit reading continuously to the last fields read */
			if (iFields >= nFields)
			{
				iFields = nFields - 1;
				return 0;
			}

			/* Indicate if there is something left to read */
			return nFields - iFields;
		}
	}

	return 0;
}

bool setTemperatureOversampling(uint8_t overSample) {
	if (overSample > BME68X_OS_16X)
		return false;

	conf.os_temp = overSample;
	int8_t rslt = bme68x_set_conf(&conf, &bme6);

	return rslt == 0;
}

bool setHumidityOversampling(uint8_t overSample) {
	if (overSample > BME68X_OS_16X)
		return false;

	conf.os_hum = overSample;

	int8_t rslt = bme68x_set_conf(&conf, &bme6);
	return rslt == 0;
}

bool setPressureOversampling(uint8_t overSample) {
	if (overSample > BME68X_OS_16X)
		return false;

	conf.os_pres = overSample;

	int8_t rslt = bme68x_set_conf(&conf, &bme6);
	return rslt == 0;
}

bool setIIRFilterSize(uint8_t filterSize) {
	if (filterSize > BME68X_FILTER_SIZE_127)
		return false;
	conf.filter = filterSize;

	int8_t rslt = bme68x_set_conf(&conf, &bme6);
	return rslt == 0;
}

bool setGasHeater(uint16_t heaterTemp, uint16_t heaterTime) {
	if ((heaterTemp == 0) || (heaterTime == 0)) {
		heatrConf.enable = BME68X_DISABLE;
	}
	else {
		heatrConf.enable = BME68X_ENABLE;
		heatrConf.heatr_temp = heaterTemp;
		heatrConf.heatr_dur = heaterTime;
	}

	int8_t rslt = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heatrConf, &bme6);
	return rslt == 0;
}

void GetGasReference(){
	int readings = 10;
	for (int i = 1; i <= readings; i++){ // read gas for 10 x 0.150mS = 1.5secs
		gasReference += readGas();
	}
	gasReference = gasReference / readings;
}

uint32_t readGas(void) {
	performReading();
	return gas_resistance;
}

bool performReading(void) {
	return endReading();
}

bool endReading(void) {
	uint32_t meas_end = beginReading();

	if (meas_end == 0) {
		return false;
	}

	int remaining_millis = remainingReadingMillis();

	if (remaining_millis > 0) {
		HAL_Delay((remaining_millis) * 2);
	}
	_meas_start = 0; /* Allow new measurement to begin */
	_meas_period = 0;

	struct bme68x_data data;
	uint8_t n_fields;

	int8_t rslt =
		bme68x_get_data(BME68X_FORCED_MODE, &data, &n_fields, &bme6);

	if (rslt != BME68X_OK)
		return false;

	if (n_fields) {
		temperature = data.temperature;
		humidity = data.humidity;
		pressure = data.pressure;
		if (data.status & (BME68X_HEAT_STAB_MSK | BME68X_GASM_VALID_MSK)) {
			gas_resistance = data.gas_resistance;
		}
		else {
			gas_resistance = 0;
		}
	}
	return true;
}

uint32_t beginReading(void) {
	if (_meas_start != 0) {
		/* A measurement is already in progress */
		return _meas_start + _meas_period;
	}

	int8_t rslt = bme68x_set_op_mode(BME68X_FORCED_MODE, &bme6);

	if (rslt != BME68X_OK)
		return false;

	/* Calculate delay period in microseconds */
	uint32_t delayUs_period = (uint32_t)bme68x_get_meas_dur(
								BME68X_FORCED_MODE, &conf, &bme6) +
							((uint32_t)heatrConf.heatr_dur * 1000);

	_meas_start = HAL_GetTick();//millis();
	_meas_period = delayUs_period / 1000;

	return _meas_start + _meas_period;
}

int remainingReadingMillis(void) {
	if (_meas_start != 0) {
		/* A measurement is already in progress */
		int remaining_time = (int)_meas_period - (HAL_GetTick() - _meas_start);
		return remaining_time < 0 ? 0 : remaining_time;
	}
	return -1;
}

float readTemperature(void) {
	performReading();
	return temperature;
}

float readPressure(void) {
	performReading();
	return pressure;
}

float readHumidity(void) {
	performReading();
	return humidity;
}

uint16_t CalculateIAQ(float score) {
  score = (100 - score) * 5;
  /*
  if      (score >= 301)                  IAQ_text += "Hazardous";
  else if (score >= 201 && score <= 300 ) IAQ_text += "Very Unhealthy";
  else if (score >= 176 && score <= 200 ) IAQ_text += "Unhealthy";
  else if (score >= 151 && score <= 175 ) IAQ_text += "Unhealthy for Sensitive Groups";
  else if (score >=  51 && score <= 150 ) IAQ_text += "Moderate";
  else if (score >=  00 && score <=  50 ) IAQ_text += "Good";
  */
  return score;
}
